/*
 *  Copyright (C) 2010 Zubanov Dmitry
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
/**
 *
 * @author Zubanov Dmitry aka javadimon, zubanov@gmail.com
 * Omsk, Russia, created 29.05.2010
 * (C) Copyright by Zubanov Dmitry
 */
package org.jssdb.core.josql;

import java.io.File;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.jssdb.core.ram.RamHandler;

public class WhereHandler {

    private static final Logger logger = Logger.getLogger(WhereHandler.class.getName());

    public ArrayList<Map> getResultArray(ArrayList<Map> resultArray, ArrayList<WhereTreeNode> whereTree) {

        ArrayList<ArrayList<Map>> outList = new ArrayList();

        int i = whereTree.size() - 1;
        while (i > -1) {
            if (!isGeneralExp(whereTree.get(i))) {
                WhereTreeNode rightNode = whereTree.get(i);
                WhereTreeNode leftNode = whereTree.get(i - 1);
                i = i - 2;
                try {
                    if (("" + leftNode.getValue()).equals("id")) {
                        outList.add(handleById(resultArray, leftNode, rightNode));

                    } else {
                        outList.add(handle(resultArray, leftNode, rightNode));
                    }

                } catch (Exception e) {
                    logger.log(Level.SEVERE, null, e);
                }
            } else {
                // TODO
                if (outList.size() == 2) {
                    resultArray = unoinResult(outList, whereTree.get(i).getValue());
                }

                i = i - 1;
            }
        }

        if (!outList.isEmpty()) {
            resultArray = formatResult(outList);
        }

        return resultArray;
    }

    private ArrayList<Map> handleById(ArrayList<Map> resultArray,
            WhereTreeNode leftNode, WhereTreeNode rightNode) {

        ArrayList<Map> resultArrayTmp = new ArrayList();

        try {
            int id = Integer.parseInt("" + rightNode.getValue());
            if (leftNode.getExpression().equals("equalsTo")) {
                for (Map m : resultArray) {
                    ArrayList<Map<Integer, Object>> list = (ArrayList) m.get(m.keySet().toArray()[0]);

                    for (Map ml : list) {
                        if (((Long) ml.keySet().toArray()[0]) == id) {
                            ArrayList<Map> aOut = new ArrayList();
                            aOut.add(ml);

                            Map mOut = new HashMap();
                            mOut.put(m.keySet().toArray()[0], aOut);
                            resultArrayTmp.add(mOut);
                        }
                    }
                }
            }

        } catch (Exception e) {
            logger.log(Level.SEVERE, null, e);

        } finally {
            return resultArrayTmp;
        }
    }

    private ArrayList<Map> formatResult(ArrayList<ArrayList<Map>> outList) {
        ArrayList<Map> resultArray = new ArrayList();
        ArrayList aOut = new ArrayList();

        // get first map array list
        ArrayList<Map> first = outList.get(0);
        for (int i = 0; i < first.size(); i++) {
            Map mf = first.get(i);
            String keyFirst = "" + mf.keySet().toArray()[0];
            ArrayList<Map<Integer, Object>> rArrayFirst = (ArrayList<Map<Integer, Object>>) mf.get(keyFirst);

            for (int j = 0; j < rArrayFirst.size(); j++) {
                Map mof = rArrayFirst.get(j);
                if (mof != null) {
                    aOut.add(mof);
                } else {
                    continue;
                }
            }

            Map<String, Object> mOut = new HashMap();
            mOut.put(keyFirst, aOut);
            resultArray.add(mOut);
        }

        outList.clear();
        return resultArray;
    }

    private ArrayList<Map> unoinResult(ArrayList<ArrayList<Map>> outList, Object expression) {
        ArrayList<Map> resultArray = new ArrayList();
        ArrayList aOut = new ArrayList();

        // get first map array list
        ArrayList<Map> first = outList.get(0);
        ArrayList<Map> second = outList.get(1);
        for (int i = 0; i < first.size(); i++) {
            Map mf = first.get(i);
            Map ms = second.get(i);
            String keyFirst = "" + mf.keySet().toArray()[0];
            ArrayList<Map<Integer, Object>> rArrayFirst = (ArrayList<Map<Integer, Object>>) mf.get(keyFirst);

            String keySecond = "" + ms.keySet().toArray()[0];
            ArrayList<Map<Integer, Object>> rArraySecond = (ArrayList<Map<Integer, Object>>) ms.get(keySecond);

            for (int j = 0; j < rArrayFirst.size(); j++) {
                Map mof = rArrayFirst.get(j);
                Map mos = rArraySecond.get(j);

                if (("" + expression).equals("AND")) {
                    if (mof != null && mos != null) {
                        aOut.add(mof);
                    } else {
                        continue;
                    }
                }

                if (("" + expression).equals("OR")) {
                    if (mof != null || mos != null) {
                        if (mof == null && mos != null) {
                            aOut.add(mos);
                        }

                        if (mof != null && mos == null) {
                            aOut.add(mof);
                        }

                    } else {
                        continue;
                    }
                }
            }

            Map<String, Object> mOut = new HashMap();
            mOut.put(keyFirst, aOut);
            resultArray.add(mOut);
        }

        outList.clear();
        return resultArray;
    }

    private boolean isGeneralExp(WhereTreeNode wtn) {
        boolean result = false;
        if (("" + wtn.getValue()).equals("AND")) {
            result = true;
        }

        if (("" + wtn.getValue()).equals("OR")) {
            result = true;
        }

        if (("" + wtn.getValue()).equals("NOT")) {
            result = true;
        }

        return result;
    }

    private ArrayList<Map> handle(ArrayList<Map> resultArray,
            WhereTreeNode leftNode, WhereTreeNode rightNode) throws Exception {
        ArrayList<Map> resultArrayOrder = (ArrayList) resultArray.clone();
        //resultArray.clear();
        ArrayList<Map> outputArray = new ArrayList<Map>();

        for (int i = 0; i < resultArrayOrder.size(); i++) {
            Map<String, Object> m = resultArrayOrder.get(i);
            String key = "" + m.keySet().toArray()[0];
            ArrayList a = (ArrayList) m.get(key);

            ArrayList aOut = new ArrayList();
            for (int j = 0; j < a.size(); j++) {
                boolean isAdd = false;

                String className = getClassName("" + leftNode.getValue());
                if (className == null) {
                    continue;
                }

                Class cls = Class.forName(className);
                String methodName = getMethodName("" + leftNode.getValue());
                for (Method method : cls.getMethods()) {
                    if (!method.getName().equals(methodName)) {
                        continue;
                    }

                    try {
                        Object k = ((Map) a.get(j)).keySet().toArray()[0];
                        Object obj = ((Map) a.get(j)).get(k);
                        if (isGeneric(obj)) {
                            isAdd = isAdd(obj, rightNode.getValue(), leftNode.getExpression(), leftNode.getExtObject());
                        } else {
                            Object val = method.invoke(obj, new Object[]{});
                            isAdd = isAdd(val, rightNode.getValue(), leftNode.getExpression(), leftNode.getExtObject());
                        }

                    } catch (Exception e) {
                        logger.log(Level.SEVERE, methodName, e);
                    }
                }

                if (isAdd) {
                    aOut.add(a.get(j));
                } else {
                    aOut.add(null);
                }

            }

            Map<String, Object> mOut = new HashMap();
            mOut.put(key, aOut);
            outputArray.add(mOut);
        }

        return outputArray;
    }

    private boolean isGeneric(Object obj) {
        boolean result = false;

        if (obj == null) {
            return result;
        }

        if (obj instanceof Boolean) {
            result = true;
        }

        if (obj instanceof Number) {
            result = true;
        }

        if (obj instanceof String) {
            result = true;
        }

        if (obj instanceof java.util.Date) {
            result = true;
        }

        return result;
    }

    private boolean isAdd(Object leftValue, Object rightValue, String comparsion,
            Object extObject) throws Exception {
        boolean result = false;

        if (leftValue instanceof Boolean) {
            if (comparsion.equals("equalsTo")) {
                boolean left = (Boolean) leftValue;
                boolean right = (Boolean) rightValue;
                if (left == right) {
                    result = true;
                }
            }
        }

        if (leftValue instanceof Short) {
            if (comparsion.equals("equalsTo")) {
                short left = (Short) leftValue;
                Number right = (Number) rightValue;
                if (left == right.shortValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThan")) {
                short left = (Short) leftValue;
                Number right = (Number) rightValue;
                if (left > right.shortValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThanEquals")) {
                short left = (Short) leftValue;
                Number right = (Number) rightValue;
                if (left >= right.shortValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThan")) {
                short left = (Short) leftValue;
                Number right = (Number) rightValue;
                if (left < right.shortValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThanEquals")) {
                short left = (Short) leftValue;
                Number right = (Number) rightValue;
                if (left <= right.shortValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("notEqualsTo")) {
                short left = (Short) leftValue;
                Number right = (Number) rightValue;
                if (left != right.shortValue()) {
                    result = true;
                }
            }
        }

        if (leftValue instanceof Integer) {
            if (comparsion.equals("equalsTo")) {
                int left = (Integer) leftValue;
                Number right = (Number) rightValue;
                if (left == right.intValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThan")) {
                int left = (Integer) leftValue;
                Number right = (Number) rightValue;
                if (left > right.intValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThanEquals")) {
                int left = (Integer) leftValue;
                Number right = (Number) rightValue;
                if (left >= right.intValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThan")) {
                int left = (Integer) leftValue;
                Number right = (Number) rightValue;
                if (left < right.intValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThanEquals")) {
                int left = (Integer) leftValue;
                Number right = (Number) rightValue;
                if (left <= right.intValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("notEqualsTo")) {
                int left = (Integer) leftValue;
                Number right = (Number) rightValue;
                if (left != right.intValue()) {
                    result = true;
                }
            }
        }

        if (leftValue instanceof Long) {
            if (comparsion.equals("equalsTo")) {
                long left = (Long) leftValue;
                Number right = (Number) rightValue;
                if (left == right.longValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThan")) {
                long left = (Long) leftValue;
                Number right = (Number) rightValue;
                if (left > right.longValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThanEquals")) {
                long left = (Long) leftValue;
                Number right = (Number) rightValue;
                if (left >= right.longValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThan")) {
                long left = (Long) leftValue;
                Number right = (Number) rightValue;
                if (left < right.longValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThanEquals")) {
                long left = (Long) leftValue;
                Number right = (Number) rightValue;
                if (left <= right.longValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("notEqualsTo")) {
                long left = (Long) leftValue;
                Number right = (Number) rightValue;
                if (left != right.longValue()) {
                    result = true;
                }
            }
        }

        if (leftValue instanceof Double) {
            if (comparsion.equals("equalsTo")) {
                double left = (Double) leftValue;
                Number right = (Number) rightValue;
                if (left == right.doubleValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThan")) {
                double left = (Double) leftValue;
                Number right = (Number) rightValue;
                if (left > right.doubleValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThanEquals")) {
                double left = (Double) leftValue;
                Number right = (Number) rightValue;
                if (left >= right.doubleValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThan")) {
                double left = (Double) leftValue;
                Number right = (Number) rightValue;
                if (left < right.doubleValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThanEquals")) {
                double left = (Double) leftValue;
                Number right = (Number) rightValue;
                if (left <= right.doubleValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("notEqualsTo")) {
                double left = (Double) leftValue;
                Number right = (Number) rightValue;
                if (left != right.doubleValue()) {
                    result = true;
                }
            }
        }

        if (leftValue instanceof Float) {
            if (comparsion.equals("equalsTo")) {
                double left = (Float) leftValue;
                Number right = (Number) rightValue;
                if (left == right.floatValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThan")) {
                double left = (Float) leftValue;
                Number right = (Number) rightValue;
                if (left > right.floatValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThanEquals")) {
                double left = (Float) leftValue;
                Number right = (Number) rightValue;
                if (left >= right.floatValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThan")) {
                double left = (Float) leftValue;
                Number right = (Number) rightValue;
                if (left < right.floatValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("monorThanEquals")) {
                double left = (Float) leftValue;
                Number right = (Number) rightValue;
                if (left <= right.floatValue()) {
                    result = true;
                }
            }

            if (comparsion.equals("notEqualsTo")) {
                double left = (Float) leftValue;
                Number right = (Number) rightValue;
                if (left != right.floatValue()) {
                    result = true;
                }
            }
        }

        if (leftValue instanceof String) {
            String left = "" + leftValue;
            String right = "" + rightValue;

            if (comparsion.equals("equalsTo")) {
                if (left.equals(right)) {
                    result = true;
                }
            }

            if (comparsion.equals("likeExpression")) {
                if ((Boolean) extObject) { // NOT LIKE
                    result = !left.matches(getRegExpression(right));

                } else { // LIKE
                    result = left.matches(getRegExpression(right));
                }
            }
        }

        if (leftValue instanceof Date) {
            if (comparsion.equals("equalsTo")) {
                java.sql.Timestamp left = new Timestamp(((Date) leftValue).getTime());
                Timestamp right = (Timestamp) rightValue;

                // timestamp right with milliseconds?
                long leftVal = 0;
                double d = right.getTime() % 1000;
                if (d == 0) {
                    // remove milliseconds from left
                    leftVal = left.getTime() - left.getTime() % 1000;
                }

                logger.log(Level.INFO, "leftValue = {0}, rightValue = {1}", new Object[]{leftVal, right.getTime()});
                if (leftVal == right.getTime()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThan")) {
                java.sql.Timestamp left = new Timestamp(((Date) leftValue).getTime());
                Timestamp right = (Timestamp) rightValue;

                // timestamp right with milliseconds?
                long leftVal = 0;
                double d = right.getTime() % 1000;
                if (d == 0) {
                    // remove milliseconds from left
                    leftVal = left.getTime() - left.getTime() % 1000;
                }

                logger.log(Level.INFO, "leftValue = {0}, rightValue = {1}", new Object[]{leftVal, right.getTime()});
                if (leftVal > right.getTime()) {
                    result = true;
                }
            }

            if (comparsion.equals("greaterThanEquals")) {
                java.sql.Timestamp left = new Timestamp(((Date) leftValue).getTime());
                Timestamp right = (Timestamp) rightValue;

                // timestamp right with milliseconds?
                long leftVal = 0;
                double d = right.getTime() % 1000;
                if (d == 0) {
                    // remove milliseconds from left
                    leftVal = left.getTime() - left.getTime() % 1000;
                }

                logger.log(Level.INFO, "leftValue = {0}, rightValue = {1}", new Object[]{leftVal, right.getTime()});
                if (leftVal >= right.getTime()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThan")) {
                java.sql.Timestamp left = new Timestamp(((Date) leftValue).getTime());
                Timestamp right = (Timestamp) rightValue;

                // timestamp right with milliseconds?
                long leftVal = 0;
                double d = right.getTime() % 1000;
                if (d == 0) {
                    // remove milliseconds from left
                    leftVal = left.getTime() - left.getTime() % 1000;
                }

                logger.log(Level.INFO, "leftValue = {0}, rightValue = {1}", new Object[]{leftVal, right.getTime()});
                if (leftVal < right.getTime()) {
                    result = true;
                }
            }

            if (comparsion.equals("minorThanEquals")) {
                java.sql.Timestamp left = new Timestamp(((Date) leftValue).getTime());
                Timestamp right = (Timestamp) rightValue;

                // timestamp right with milliseconds?
                long leftVal = 0;
                double d = right.getTime() % 1000;
                if (d == 0) {
                    // remove milliseconds from left
                    leftVal = left.getTime() - left.getTime() % 1000;
                }

                logger.log(Level.INFO, "leftValue = {0}, rightValue = {1}", new Object[]{leftVal, right.getTime()});
                if (leftVal <= right.getTime()) {
                    result = true;
                }
            }

            if (comparsion.equals("notEqualsTo")) {
                java.sql.Timestamp left = new Timestamp(((Date) leftValue).getTime());
                Timestamp right = (Timestamp) rightValue;

                // timestamp right with milliseconds?
                long leftVal = 0;
                double d = right.getTime() % 1000;
                if (d == 0) {
                    // remove milliseconds from left
                    leftVal = left.getTime() - left.getTime() % 1000;
                }

                logger.log(Level.INFO, "leftValue = {0}, rightValue = {1}", new Object[]{leftVal, right.getTime()});
                if (leftVal != right.getTime()) {
                    result = true;
                }
            }
        }

        return result;
    }

    private String getRegExpression(String str) {
        logger.info(str);
        String regExp = "";
        regExp = str.replaceAll("%", "(.*?)");
        regExp = regExp.replaceAll("_", "(.{1})");
        logger.info(regExp);
        return regExp;
    }

    private String getMethodName(String className) {
        String arr[] = className.split("\\.");
        return arr[arr.length - 1];
    }

    private String getClassName(String className) {
        String result = "";
        String arr[] = className.split("\\.");
        for (int i = 0; i < arr.length - 1; i++) {
            result = result + arr[i] + ".";
        }

        result = result.substring(0, result.length() - 1);

        if (!isClass(result)) {
            result = null;
        }

        return result;
    }

    private boolean isClass(String className) {
        boolean result = true;
        try {
            Class cls = Class.forName(className);
        } catch (ClassNotFoundException ex) {
            result = false;
        }
        return result;
    }

    private boolean isExpression(WhereTreeNode wtn) {
        boolean result = false;

        return result;
    }

    private ArrayList<Map> getResultArray(WhereTreeNode leftNode,
            WhereTreeNode rightNode, ArrayList<Map> resultArray) {
        return resultArray;
    }
}
